﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IrcConnection.cs" company="LeetSoftwerx">
//   Copyright ©2012 LeetSoftwerx
// </copyright>
// <summary>
//   The private message recieved event args.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Irclib
{
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Diagnostics.Contracts;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Runtime.Remoting.Messaging;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading;

    using Irclib.Properties;

    /// <summary>
    /// The private message recieved event args.
    /// </summary>
    internal sealed class PrivateMessageRecievedEventArgs : EventArgs
    {
        #region Constants and Fields

        /// <summary>
        /// The _raw message.
        /// </summary>
        private string _rawMessage;

        /// <summary>
        /// The _sender.
        /// </summary>
        private string _sender;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="PrivateMessageRecievedEventArgs"/> class.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="message">
        /// The message.
        /// </param>
        public PrivateMessageRecievedEventArgs(string sender, string message)
            : base()
        {
            this._sender = sender;
            this._rawMessage = message;
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///   The contents of the message
        /// </summary>
        public string Message
        {
            get
            {
                return this._rawMessage;
            }
        }

        /// <summary>
        ///   The sender of the message
        /// </summary>
        public string Sender
        {
            get
            {
                return this._sender;
            }
        }

        #endregion
    }

    /// <summary>
    /// A connection to an IRC server, this variety is unsecure
    /// </summary>
    public class IrcConnection : IDisposable, IIrcConnection
    {
        #region Constants and Fields

        /// <summary>
        ///   The maximum length of an IRC message not including \r\n
        /// </summary>
        public const int IrcMessageLength = 510;

        /// <summary>
        ///   Dummy channel name used to find the default channel for the server
        /// </summary>
        private const string SERVER_CHANNEL = "~Default";

        /// <summary>
        /// The _channel handlers.
        /// </summary>
        private Dictionary<string, EventHandler<PrivateMessageRecievedEventArgs>> _channelHandlers;

        /// <summary>
        /// The _client.
        /// </summary>
        private TcpClient client;

        /// <summary>
        /// The _connected.
        /// </summary>
        private bool connected;

        /// <summary>
        /// The _host name.
        /// </summary>
        private string _hostName;

        /// <summary>
        /// The _nickname.
        /// </summary>
        private string _nickname;

        /// <summary>
        /// The _reader.
        /// </summary>
        private TextReader reader;

        /// <summary>
        /// The _server.
        /// </summary>
        private readonly Uri _server;

        /// <summary>
        /// The _server channel.
        /// </summary>
        private IrcChannel _serverChannel;

        /// <summary>
        /// The _server encoding.
        /// </summary>
        private Encoding _serverEncoding;

        /// <summary>
        /// The _server name.
        /// </summary>
        private string _serverName;

        /// <summary>
        /// The _user name.
        /// </summary>
        private string _userName;

        /// <summary>
        /// The _writer.
        /// </summary>
        private TextWriter _writer;

        /// <summary>
        /// The keep pinging.
        /// </summary>
        private bool keepPinging = true;

        /// <summary>
        /// The ping thread.
        /// </summary>
        private Thread pingThread;

        /// <summary>
        /// The receive.
        /// </summary>
        private bool receive = true;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="IrcConnection"/> class. 
        /// Creates a new IrcConnection
        /// </summary>
        /// <param name="server">
        /// The URI of the server, must contain port information 
        /// </param>
        /// <param name="serverEncoding">
        /// The character encoding of the server 
        /// </param>
        /// <param name="userName">
        /// The requested user name to send to the server 
        /// </param>
        public IrcConnection(Uri server, Encoding serverEncoding, string userName)
        {
            // input validation
            Contract.Requires(server != null);
            Contract.Requires(serverEncoding != null);
            Contract.Requires(!string.IsNullOrWhiteSpace(userName) && Regex.IsMatch(userName, @"(?<=[\w\[\]\\`_^\{\|}])[\w\d]*)"));

            // assign basic instance variables
            this._server = server;
            this._serverEncoding = serverEncoding;
            this._userName = userName;
            this._channelHandlers = new Dictionary<string, EventHandler<PrivateMessageRecievedEventArgs>>();

            // add the default channel to the channel handlers
            this._serverChannel = new IrcChannel(this);
            this._channelHandlers.Add(IrcConnection.SERVER_CHANNEL, this._serverChannel.OnMessageRecieved);
            this.connected = false;
        }

        #endregion

        #region Public Events

        /// <summary>
        ///   Event triggered if the server allows for the use of virtual hosts after the vHost is activated
        /// </summary>
        public event EventHandler HostNameChanged;

        #endregion

        #region Enums

        /// <summary>
        /// These are the numeric replies of the server
        /// </summary>
        private enum IrcResponseCode : int
        {
            /// <summary>
            /// The null.
            /// </summary>
            NULL = 0, 

            /// <summary>
            /// The rp l_ tracelink.
            /// </summary>
            RPL_TRACELINK = 200, 

            /// <summary>
            /// The rp l_ traceconnecting.
            /// </summary>
            RPL_TRACECONNECTING = 201, 

            /// <summary>
            /// The rp l_ tracehandshake.
            /// </summary>
            RPL_TRACEHANDSHAKE = 202, 

            /// <summary>
            /// The rp l_ traceunknown.
            /// </summary>
            RPL_TRACEUNKNOWN = 203, 

            /// <summary>
            /// The rp l_ traceoperator.
            /// </summary>
            RPL_TRACEOPERATOR = 204, 

            /// <summary>
            /// The rp l_ traceuser.
            /// </summary>
            RPL_TRACEUSER = 205, 

            /// <summary>
            /// The rp l_ traceserver.
            /// </summary>
            RPL_TRACESERVER = 206, 

            // no 207
            /// <summary>
            /// The rp l_ tracenewtype.
            /// </summary>
            RPL_TRACENEWTYPE = 208, 

            /// <summary>
            ///   reserved
            /// </summary>
            RPL_TRACECLASS = 209, 

            // no 210
            /// <summary>
            /// The rp l_ statslinkinfo.
            /// </summary>
            RPL_STATSLINKINFO = 211, 

            /// <summary>
            /// The rp l_ statscommands.
            /// </summary>
            RPL_STATSCOMMANDS = 212, 

            /// <summary>
            /// The rp l_ statscline.
            /// </summary>
            RPL_STATSCLINE = 213, 

            /// <summary>
            /// The rp l_ statsnline.
            /// </summary>
            RPL_STATSNLINE = 214, 

            /// <summary>
            /// The rp l_ statsiline.
            /// </summary>
            RPL_STATSILINE = 215, 

            /// <summary>
            /// The rp l_ statskline.
            /// </summary>
            RPL_STATSKLINE = 216, 

            /// <summary>
            ///   reserved
            /// </summary>
            RPL_STATSQLINE = 217, 

            /// <summary>
            /// The rp l_ statsyline.
            /// </summary>
            RPL_STATSYLINE = 218, 

            /// <summary>
            /// The rp l_ endofstats.
            /// </summary>
            RPL_ENDOFSTATS = 219, 

            // no 220
            /// <summary>
            /// The rp l_ umodeis.
            /// </summary>
            RPL_UMODEIS = 221, 

            // 222-230 don't exist
            /// <summary>
            ///   reserved
            /// </summary>
            RPL_SERVICEINFO = 231, 

            /// <summary>
            ///   reserved
            /// </summary>
            RPL_ENDOFSERVICES = 232, 

            /// <summary>
            ///   reserved
            /// </summary>
            RPL_SERVICE = 233, 

            /// <summary>
            ///   reserved
            /// </summary>
            RPL_SERVLIST = 234, 

            /// <summary>
            /// The rp l_ servlistend.
            /// </summary>
            RPL_SERVLISTEND = 235, 

            // 236-240 don't exist
            /// <summary>
            /// The rp l_ statslline.
            /// </summary>
            RPL_STATSLLINE = 241, 

            /// <summary>
            /// The rp l_ statsuptime.
            /// </summary>
            RPL_STATSUPTIME = 242, 

            /// <summary>
            /// The rp l_ statsoline.
            /// </summary>
            RPL_STATSOLINE = 243, 

            /// <summary>
            /// The rp l_ statshline.
            /// </summary>
            RPL_STATSHLINE = 244, 

            // 245-250 don't exist
            /// <summary>
            /// The rp l_ luserclient.
            /// </summary>
            RPL_LUSERCLIENT = 251, 

            /// <summary>
            /// The rp l_ luserop.
            /// </summary>
            RPL_LUSEROP = 252, 

            /// <summary>
            /// The rp l_ luserunknown.
            /// </summary>
            RPL_LUSERUNKNOWN = 253, 

            /// <summary>
            /// The rp l_ luserchannels.
            /// </summary>
            RPL_LUSERCHANNELS = 254, 

            /// <summary>
            ///   In processing an LUSERS message, the server sends a set of replies from RPL_LUSERCLIENT, RPL_LUSEROP, RPL_USERUNKNOWN, RPL_LUSERCHANNELS and RPL_LUSERME. When replying, a server must send back RPL_LUSERCLIENT and RPL_LUSERME. The other replies are only sent back if a non-zero count is found for them.
            /// </summary>
            RPL_LUSERME = 255, 

            /// <summary>
            /// The rp l_ adminme.
            /// </summary>
            RPL_ADMINME = 256, 

            /// <summary>
            /// The rp l_ adminlo c 1.
            /// </summary>
            RPL_ADMINLOC1 = 257, 

            /// <summary>
            /// The rp l_ adminlo c 2.
            /// </summary>
            RPL_ADMINLOC2 = 258, 

            /// <summary>
            ///   When replying to an ADMIN message, a server is expected to use replies RLP_ADMINME through to RPL_ADMINEMAIL and provide a text message with each. For RPL_ADMINLOC1 a description of what city, state and country the server is in is expected, followed by details of the university and department (RPL_ADMINLOC2) and finally the administrative contact for the server (an email address here is required) in RPL_ADMINEMAIL.
            /// </summary>
            RPL_ADMINEMAIL = 259, 

            /// <summary>
            /// The rp l_ none.
            /// </summary>
            RPL_NONE = 300, 

            /// <summary>
            /// The rp l_ away.
            /// </summary>
            RPL_AWAY = 301, 

            /// <summary>
            /// The rp l_ userhost.
            /// </summary>
            RPL_USERHOST = 302, 

            /// <summary>
            /// The rp l_ ison.
            /// </summary>
            RPL_ISON = 303, 

            /// <summary>
            /// The rp l_ unaway.
            /// </summary>
            RPL_UNAWAY = 305, 

            /// <summary>
            /// The rp l_ nowaway.
            /// </summary>
            RPL_NOWAWAY = 306, 

            // 307-310 don't exist
            /// <summary>
            /// The rp l_ whoisuser.
            /// </summary>
            RPL_WHOISUSER = 311, 

            /// <summary>
            /// The rp l_ whoisserver.
            /// </summary>
            RPL_WHOISSERVER = 312, 

            /// <summary>
            /// The rp l_ whoisoperator.
            /// </summary>
            RPL_WHOISOPERATOR = 313, 

            /// <summary>
            /// The rp l_ whowasuser.
            /// </summary>
            RPL_WHOWASUSER = 314, 

            /// <summary>
            /// The rp l_ endofwho.
            /// </summary>
            RPL_ENDOFWHO = 315, 

            /// <summary>
            ///   reserved
            /// </summary>
            RPL_WHOISCHANOP = 316, 

            /// <summary>
            /// The rp l_ whoisidle.
            /// </summary>
            RPL_WHOISIDLE = 317, 

            /// <summary>
            /// The rp l_ endofwhois.
            /// </summary>
            RPL_ENDOFWHOIS = 318, 

            /// <summary>
            /// The rp l_ whoischannels.
            /// </summary>
            RPL_WHOISCHANNELS = 319, 

            // no 320
            /// <summary>
            /// The rp l_ liststart.
            /// </summary>
            RPL_LISTSTART = 321, 

            /// <summary>
            /// The rp l_ list.
            /// </summary>
            RPL_LIST = 322, 

            /// <summary>
            /// The rp l_ listend.
            /// </summary>
            RPL_LISTEND = 323, 

            /// <summary>
            /// The rp l_ channelmodeis.
            /// </summary>
            RPL_CHANNELMODEIS = 324, 

            // 325-330 don't exist
            /// <summary>
            /// The rp l_ notopic.
            /// </summary>
            RPL_NOTOPIC = 331, 

            /// <summary>
            /// The rp l_ topic.
            /// </summary>
            RPL_TOPIC = 332, 

            // 333-340 don't exist
            /// <summary>
            /// The rp l_ inviting.
            /// </summary>
            RPL_INVITING = 341, 

            /// <summary>
            /// The rp l_ summoning.
            /// </summary>
            RPL_SUMMONING = 342, 

            // 343-350 don't exist
            /// <summary>
            /// The rp l_ version.
            /// </summary>
            RPL_VERSION = 351, 

            /// <summary>
            /// The rp l_ whoreply.
            /// </summary>
            RPL_WHOREPLY = 352, 

            /// <summary>
            /// The rp l_ namreply.
            /// </summary>
            RPL_NAMREPLY = 353, 

            // 354 - 360 don't exist
            /// <summary>
            ///   reserved
            /// </summary>
            RPL_KILLDONE = 361, 

            /// <summary>
            ///   reserved
            /// </summary>
            RPL_CLOSING = 362, 

            /// <summary>
            ///   reserved
            /// </summary>
            RPL_CLOSEEND = 363, 

            /// <summary>
            /// The rp l_ links.
            /// </summary>
            RPL_LINKS = 364, 

            /// <summary>
            ///   In replying to the LINKS message, a server must send replies back using the RPL_LINKS numeric and mark the end of the list using an RPL_ENDOFLINKS reply.v
            /// </summary>
            RPL_ENDOFLINKS = 365, 

            /// <summary>
            ///   To reply to a NAMES message, a reply pair consisting of RPL_NAMREPLY and RPL_ENDOFNAMES is sent by the server back to the client. If there is no channel found as in the query, then only RPL_ENDOFNAMES is returned. The exception to this is when a NAMES message is sent with no parameters and all visible channels and contents are sent back in a series of RPL_NAMEREPLY messages with a RPL_ENDOFNAMES to mark the end.
            /// </summary>
            RPL_ENDOFNAMES = 366, 

            /// <summary>
            /// The rp l_ banlist.
            /// </summary>
            RPL_BANLIST = 367, 

            /// <summary>
            ///   When listing the active 'bans' for a given channel, a server is required to send the list back using the RPL_BANLIST and RPL_ENDOFBANLIST messages. A separate RPL_BANLIST is sent for each active banid. After the banids have been listed (or if none present) a RPL_ENDOFBANLIST must be sent.
            /// </summary>
            RPL_ENDOFBANLIST = 368, 

            /// <summary>
            ///   When replying to a WHOWAS message, a server must use the replies RPL_WHOWASUSER, RPL_WHOISSERVER or ERR_WASNOSUCHNICK for each nickname in the presented list. At the end of all reply batches, there must be RPL_ENDOFWHOWAS (even if there was only one reply and it was an error).
            /// </summary>
            RPL_ENDOFWHOWAS = 369, 

            // no 370
            /// <summary>
            /// The rp l_ info.
            /// </summary>
            RPL_INFO = 371, 

            /// <summary>
            /// The rp l_ motd.
            /// </summary>
            RPL_MOTD = 372, 

            /// <summary>
            ///   reserved
            /// </summary>
            RPL_INFOSTART = 373, 

            /// <summary>
            ///   A server responding to an INFO message is required to send all its 'info' in a series of RPL_INFO messages with a RPL_ENDOFINFO reply to indicate the end of the replies
            /// </summary>
            RPL_ENDOFINFO = 374, 

            /// <summary>
            /// The rp l_ motdstart.
            /// </summary>
            RPL_MOTDSTART = 375, 

            /// <summary>
            ///   When responding to the MOTD message and the MOTD file is found, the file is displayed line by line, with each line no longer than 80 characters, using RPL_MOTD format replies. These should be surrounded by a RPL_MOTDSTART (before the RPL_MOTDs) and an RPL_ENDOFMOTD (after).
            /// </summary>
            RPL_ENDOFMOTD = 376, 

            // 377-380 don't exist
            /// <summary>
            /// The rp l_ youreoper.
            /// </summary>
            RPL_YOUREOPER = 381, 

            /// <summary>
            ///   If the REHASH option is used and an operator sends a REHASH message, an RPL_REHASHING is sent back to the operator.
            /// </summary>
            RPL_REHASHING = 382, 

            /// <summary>
            ///   Reserved
            /// </summary>
            RPL_MYPORTIS = 384, 

            // 385-390 don't exist
            /// <summary>
            /// The rp l_ time.
            /// </summary>
            RPL_TIME = 391, 

            /// <summary>
            /// The rp l_ usersstart.
            /// </summary>
            RPL_USERSSTART = 392, 

            /// <summary>
            /// The rp l_ users.
            /// </summary>
            RPL_USERS = 393, 

            /// <summary>
            /// The rp l_ endofusers.
            /// </summary>
            RPL_ENDOFUSERS = 394, 

            /// <summary>
            /// The rp l_ nousers.
            /// </summary>
            RPL_NOUSERS = 395, 

            // 396-400 don't exist
            /// <summary>
            /// The er r_ nosuchnick.
            /// </summary>
            ERR_NOSUCHNICK = 401, 

            /// <summary>
            /// The er r_ nosuchserver.
            /// </summary>
            ERR_NOSUCHSERVER = 402, 

            /// <summary>
            /// The er r_ nosuchchannel.
            /// </summary>
            ERR_NOSUCHCHANNEL = 403, 

            /// <summary>
            ///   Sent to a user who is either (a) not on a channel which is mode +n or (b) not a chanop (or mode +v) on a channel which has mode +m set and is trying to send a PRIVMSG message to that channel.
            /// </summary>
            ERR_CANNOTSENDTOCHAN = 404, 

            /// <summary>
            ///   Sent to a user when they have joined the maximum number of allowed channels and they try to join another channel.
            /// </summary>
            ERR_TOOMANYCHANNELS = 405, 

            /// <summary>
            ///   Returned by WHOWAS to indicate there is no history information for that nickname.
            /// </summary>
            ERR_WASNOSUCHNICK = 406, 

            /// <summary>
            ///   Returned to a client which is attempting to send PRIVMSG/NOTICE using the user@host destination format and for a user@host which has several occurrences.
            /// </summary>
            ERR_TOOMANYTARGETS = 407, 

            // no 408
            /// <summary>
            ///   PING or PONG message missing the originator parameter which is required since these commands must work without valid prefixes.
            /// </summary>
            ERR_NOORIGIN = 409, 

            // no 410
            /// <summary>
            /// The er r_ norecipient.
            /// </summary>
            ERR_NORECIPIENT = 411, 

            /// <summary>
            /// The er r_ notexttosend.
            /// </summary>
            ERR_NOTEXTTOSEND = 412, 

            /// <summary>
            /// The er r_ notoplevel.
            /// </summary>
            ERR_NOTOPLEVEL = 413, 

            /// <summary>
            ///   412 - 414 are returned by PRIVMSG to indicate that the message wasn't delivered for some reason. ERR_NOTOPLEVEL and ERR_WILDTOPLEVEL are errors that are returned when an invalid use of "PRIVMSG $&lt;server&gt;" or "PRIVMSG #&lt;host&gt;" is attempted.
            /// </summary>
            ERR_WILDTOPLEVEL = 414, 

            // no 415-420
            /// <summary>
            ///   Returned to a registered client to indicate that the command sent is unknown by the server.
            /// </summary>
            ERR_UNKNOWNCOMMAND = 421, 

            /// <summary>
            ///   Server's MOTD file could not be opened by the server.
            /// </summary>
            ERR_NOMOTD = 422, 

            /// <summary>
            ///   Returned by a server in response to an ADMIN message when there is an error in finding the appropriate information.
            /// </summary>
            ERR_NOADMININFO = 423, 

            /// <summary>
            ///   Generic error message used to report a failed file operation during the processing of a message.
            /// </summary>
            ERR_FILEERROR = 424, 

            // 425-430 don't exist
            /// <summary>
            ///   Returned when a nickname parameter expected for a command and isn't found.
            /// </summary>
            ERR_NONICKNAMEGIVEN = 431, 

            /// <summary>
            ///   Returned after receiving a NICK message which contains characters which do not fall in the defined set.
            /// </summary>
            ERR_ERRONEUSNICKNAME = 432, 

            /// <summary>
            ///   Returned when a NICK message is processed that results in an attempt to change to a currently existing nickname.
            /// </summary>
            ERR_NICKNAMEINUSE = 433, 

            // 434-435 don't exist
            /// <summary>
            ///   Returned by a server to a client when it detects a nickname collision (registered of a NICK that already exists by another server).
            /// </summary>
            ERR_NICKCOLLISION = 436, 

            // 437-440 don't exist
            /// <summary>
            ///   Returned by the server to indicate that the target user of the command is not on the given channel.
            /// </summary>
            ERR_USERNOTINCHANNEL = 441, 

            /// <summary>
            ///   Returned by the server whenever a client tries to perform a channel effecting command for which the client isn't a member.
            /// </summary>
            ERR_NOTONCHANNEL = 442, 

            /// <summary>
            ///   Returned when a client tries to invite a user to a channel they are already on.
            /// </summary>
            ERR_USERONCHANNEL = 443, 

            /// <summary>
            ///   Returned by the summon after a SUMMON command for a user was unable to be performed since they were not logged in.
            /// </summary>
            ERR_NOLOGIN = 444, 

            /// <summary>
            ///   Returned as a response to the SUMMON command. Must be returned by any server which does not implement it.
            /// </summary>
            ERR_SUMMONDISABLED = 445, 

            /// <summary>
            ///   Returned as a response to the USERS command. Must be returned by any server which does not implement it.
            /// </summary>
            ERR_USERSDISABLED = 446, 

            // 447-450 don't exist
            /// <summary>
            ///   Returned by the server to indicate that the client must be registered before the server will allow it to be parsed in detail.
            /// </summary>
            ERR_NOTREGISTERED = 451, 

            // 452-460 don't exist
            /// <summary>
            ///   Returned by the server by numerous commands to indicate to the client that it didn't supply enough parameters.
            /// </summary>
            ERR_NEEDMOREPARAMS = 461, 

            /// <summary>
            ///   Returned by the server to any link which tries to change part of the registered details (such as password or user details from second USER message).
            /// </summary>
            ERR_ALREADYREGISTRED = 462, 

            /// <summary>
            ///   Returned to a client which attempts to register with a server which does not been setup to allow connections from the host the attempted connection is tried.
            /// </summary>
            ERR_NOPERMFORHOST = 463, 

            /// <summary>
            ///   Returned to indicate a failed attempt at registering a connection for which a password was required and was either not given or incorrect.
            /// </summary>
            ERR_PASSWDMISMATCH = 464, 

            /// <summary>
            ///   Returned after an attempt to connect and register yourself with a server which has been setup to explicitly deny connections to you.
            /// </summary>
            /// <remarks>
            ///   The text and name of this reply should be verified against RFC 1459
            /// </remarks>
            ERR_YOUREBANNEDCREEP = 465, 

            /// <summary>
            ///   reserved
            /// </summary>
            ERR_YOUWILLBEBANNED = 466, 

            /// <summary>
            /// The er r_ keyset.
            /// </summary>
            ERR_KEYSET = 467, 

            // 468-470 don't exist
            /// <summary>
            /// The er r_ channelisfull.
            /// </summary>
            ERR_CHANNELISFULL = 471, 

            /// <summary>
            /// The er r_ unknownmode.
            /// </summary>
            ERR_UNKNOWNMODE = 472, 

            /// <summary>
            /// The er r_ inviteonlychan.
            /// </summary>
            ERR_INVITEONLYCHAN = 473, 

            /// <summary>
            /// The er r_ bannedfromchan.
            /// </summary>
            ERR_BANNEDFROMCHAN = 474, 

            /// <summary>
            /// The er r_ badchannelkey.
            /// </summary>
            ERR_BADCHANNELKEY = 475, 

            /// <summary>
            ///   reserved
            /// </summary>
            ERR_BADCHANMASK = 476, 

            // 477-480 don't exist
            /// <summary>
            ///   Any command requiring operator privileges to operate must return this error to indicate the attempt was unsuccessful.
            /// </summary>
            ERR_NOPRIVILEGES = 481, 

            /// <summary>
            ///   Any command requiring 'chanop' privileges (such as MODE messages) must return this error if the client making the attempt is not a chanop on the specified channel.
            /// </summary>
            ERR_CHANOPRIVSNEEDED = 482, 

            /// <summary>
            ///   Any attempts to use the KILL command on a server are to be refused and this error returned directly to the client.
            /// </summary>
            ERR_CANTKILLSERVER = 483, 

            // 484-490 don't exist
            /// <summary>
            ///   If a client sends an OPER message and the server has not been configured to allow connections from the client's host as an operator, this error must be returned.
            /// </summary>
            ERR_NOOPERHOST = 491, 

            /// <summary>
            ///   reserved
            /// </summary>
            ERR_NOSERVICEHOST = 492, 

            // 493-500 don't exist
            /// <summary>
            ///   Returned by the server to indicate that a MODE message was sent with a nickname parameter and that the a mode flag sent was not recognized.
            /// </summary>
            ERR_UMODEUNKNOWNFLAG = 501, 

            /// <summary>
            ///   Error sent to any user trying to view or change the user mode for a user other than themselves.
            /// </summary>
            ERR_USERSDONTMATCH = 502
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///   Gets whether or not the connection is active
        /// </summary>
        public bool Connected
        {
            get
            {
                return this.connected;
            }
        }

        /// <summary>
        ///   Gets the current hostname of the user from the perspective of the server
        /// </summary>
        public string HostName
        {
            get
            {
                return this._hostName;
            }
        }

        /// <summary>
        ///   Gets or sets the users Nickname
        /// </summary>
        public string Nickname
        {
            get
            {
                return this._nickname;
            }

            set
            {
                const string regex = @"^[A-Za-z][A-Za-z0-9-\[\]\\`\^\{\}]+";
                RegexStringValidator validator = new RegexStringValidator(regex);

                /* this will throw an argument exception if its invalid
                 * we will let this propagate to let the caller know that the 
                 * nick is invalid
                 */
                validator.Validate(value);
                string response = this.Send(string.Format(CultureInfo.InvariantCulture, Resources.NICK, value));
                int numericResponse = 0;
                int.TryParse(response.Substring(0, response.IndexOf(' ')), out numericResponse);
                if (Resources.ERR_NICKNAMEINUSE != numericResponse)
                {
                    this._nickname = value;
                }
            }
        }

        /// <summary>
        ///   Gets the name of the server
        /// </summary>
        public string ServerName
        {
            get
            {
                return this._serverName;
            }
        }

        #endregion

        #region Properties

        /// <summary>
        ///   The encoding of the server
        /// </summary>
        public Encoding ServerEncoding
        {
            get
            {
                return this._serverEncoding;
            }
        }

        /// <summary>
        ///   The underlying TcpClient
        /// </summary>
        protected TcpClient Client
        {
            get
            {
                return this.client;
            }
        }

        /// <summary>
        ///   The URI for the server
        /// </summary>
        public Uri Server
        {
            get
            {
                return this._server;
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// List channels and their topics.
        /// </summary>
        /// <param name="channels">
        /// A list of specific channels to get the status of, or null to get all 
        /// </param>
        /// <returns>
        /// </returns>
        public IEnumerable<ChannelInfo> ChannelList(IEnumerable<string> channels)
        {
            this._serverChannel.SendMessage(string.Format(Resources.LIST, string.Join(",", channels.ToArray())));

            // while (this._serverChannel.InbountMessageQueue.FirstOrDefault(t => t.Contains(string.Format(IrcReturns.RPL_LISTEND, this.Nickname))) != null)
            // Thread.Sleep(2000);
            throw new NotImplementedException();
        }

        /// <summary>
        /// Connect to the server
        /// </summary>
        public virtual void Connect()
        {
            this.client = new TcpClient(this._server.DnsSafeHost, this._server.Port);
            this.pingThread = new Thread(new ThreadStart(this.PingServer));
        }

        /// <summary>
        /// Closes and releases all resources held by the connection
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Initialize the server for commands
        /// </summary>
        /// <remarks>
        /// must be run after Connect()
        /// </remarks>
        public virtual void Initialize()
        {
            this.Initialize(this.client.GetStream());
        }

        /// <summary>
        /// Join an IRC channel
        /// </summary>
        /// <param name="channelName">
        /// The name of the channel to join 
        /// </param>
        /// <returns>
        /// The IrcChannel Object that encapsulates the functionality necessary to interact with that channel 
        /// </returns>
        public IrcChannel Join(string channelName)
        {
            IrcChannel toReturn = new IrcChannel(channelName, this);
            this._channelHandlers.Add(
                toReturn.ChannelName, new EventHandler<PrivateMessageRecievedEventArgs>(toReturn.OnMessageRecieved));
            return toReturn;
        }

        /// <summary>
        /// Sends the ping command to the specified server
        /// </summary>
        public void Ping()
        {
            this._writer.WriteLine(string.Format(CultureInfo.InvariantCulture, Resources.PING, DateTime.Now.Ticks));
            this._writer.Flush();
        }

        /// <summary>
        /// Sends a line to the server
        /// </summary>
        /// <param name="toWrite">
        /// The message to send to the server, should not exceed 510 characters total 
        /// </param>
        /// <returns>
        /// The server response 
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// Thrown if the message is null
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// The length of toWrite exceeds 510 characters
        /// </exception>
        public string Send(string toWrite)
        {
            if (toWrite == null)
            {
                throw new ArgumentNullException("toWrite", "The line to write cannot be null");
            }

            if (toWrite.Length > 510)
            {
                throw new ArgumentOutOfRangeException(
                    "toWrite", "Length of the message cannot exceed " + IrcConnection.IrcMessageLength + " Characters");
            }

            // send the line to the server
            this._writer.WriteLine(toWrite);
            this._writer.Flush();

            // return the server's response
            return this.reader.ReadLine();
        }

        /// <summary>
        /// Begins pinging the server every 15 sec, required to prevent timeout
        /// </summary>
        /// <param name="serverName">
        /// The server Name.
        /// </param>
        public void StartPing(string serverName)
        {
            this.pingThread.Start();
            this._serverName = serverName;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Sends all the messages in a particular channel's send queue
        /// </summary>
        /// <param name="sender">
        /// The sending Channel 
        /// </param>
        /// <param name="e">
        /// The event arguments for the send 
        /// </param>
        internal void OnChannelSend(object sender, EventArgs e)
        {
            IrcChannel sendingChannel = sender as IrcChannel;
            foreach (string message in sendingChannel.OutboundQueue)
            {
                this._writer.WriteLine(message);
            }

            this._writer.Flush();
        }

        /// <summary>
        /// closes and releases any resources held by the connection, this MUST be called in any derived class
        /// </summary>
        /// <param name="disposing">
        /// Release managed resources 
        /// </param>
        protected virtual void Dispose(bool disposing)
        {
            this.connected = false;
            this.keepPinging = false;
            if (disposing)
            {
                if (this.reader != null)
                {
                    this.reader.Close();
                    this.reader = null;
                }

                if (this._writer != null)
                {
                    this._writer.Close();
                    this._writer = null;
                }

                if (this.client != null)
                {
                    this.client.Close();
                    this.client = null;
                }
            }
        }

        /// <summary>
        /// Initializes the connection, this function must be called before any communication with the server but after connect
        /// </summary>
        /// <param name="theStream">
        /// The stream that is connected to the server 
        /// </param>
        protected void Initialize(Stream theStream)
        {
            this.reader = new StreamReader(theStream, this._serverEncoding);
            this._writer = new StreamWriter(theStream, this._serverEncoding);

            this._writer.WriteLine(Resources.NICK + this._userName);
            this._writer.WriteLine(
                string.Format(CultureInfo.InvariantCulture, Resources.USER, this._userName, "barfoo", "foo", "bar"));
            if (this.client.Client.LocalEndPoint is IPEndPoint)
            {
                IPEndPoint endpoint = new IPEndPoint((this.client.Client.LocalEndPoint as IPEndPoint).Address, 113);
                TcpListener listener = new TcpListener(endpoint);
                try
                {
                    listener.Start();
                    if (listener.Pending())
                    {
                        using (TcpClient identd = listener.AcceptTcpClient())
                        {
                            int serverPort, clientPort;
                            using (StreamReader queryReader = new StreamReader(identd.GetStream(), Encoding.ASCII))
                            {
                                string query = queryReader.ReadLine();
                                int commaIndex = query.IndexOf(',');
                                NumberStyles parseStyles = NumberStyles.AllowLeadingWhite
                                                           | NumberStyles.AllowTrailingWhite;
                                serverPort = int.Parse(
                                    query.Substring(0, commaIndex), parseStyles, CultureInfo.InvariantCulture);
                                clientPort = int.Parse(
                                    query.Substring(commaIndex + 1), parseStyles, CultureInfo.InvariantCulture);
                            }

                            using (StreamWriter responseWriter = new StreamWriter(identd.GetStream()))
                            {
                                responseWriter.WriteLine(Resources.IDENTD, serverPort, clientPort, this._userName);
                            }
                        }
                    }
                }
                finally
                {
                    listener.Stop();
                }
            }

            this.pingThread.Start();
            this.connected = true;
        }

        /// <summary>
        /// The extract leading string from message.
        /// </summary>
        /// <param name="message">
        /// The message.
        /// </param>
        /// <returns>
        /// The extract leading string from message.
        /// </returns>
        private static string ExtractLeadingStringFromMessage(string message)
        {
            return message.Substring(3, message.IndexOf(':') - 3);
        }

        /// <summary>
        /// The get localized response for numeric reply.
        /// </summary>
        /// <param name="response">
        /// The response.
        /// </param>
        /// <param name="message">
        /// The message.
        /// </param>
        /// <returns>
        /// The get localized response for numeric reply.
        /// </returns>
        private static string GetLocalizedResponseForNumericReply(int response, string message)
        {
            switch ((IrcResponseCode)response)
            {
                case IrcResponseCode.ERR_NOSUCHNICK:
                    return string.Format(
                        CultureInfo.CurrentUICulture, 
                        IrcReturns.ERR_NOSUCHNICK, 
                        ExtractLeadingStringFromMessage(message));
                case IrcResponseCode.ERR_NOSUCHCHANNEL:
                    return string.Format(
                        CultureInfo.CurrentUICulture, 
                        IrcReturns.ERR_NOSUCHCHANNEL, 
                        ExtractLeadingStringFromMessage(message));
                case IrcResponseCode.ERR_CANNOTSENDTOCHAN:
                    return string.Format(
                        CultureInfo.CurrentUICulture, 
                        IrcReturns.ERR_CANNOTSENDTOCHAN, 
                        ExtractLeadingStringFromMessage(message));
                case IrcResponseCode.ERR_TOOMANYCHANNELS:
                    return string.Format(
                        CultureInfo.CurrentUICulture, 
                        IrcReturns.ERR_TOOMANYCHANNELS, 
                        ExtractLeadingStringFromMessage(message));
                case IrcResponseCode.ERR_NICKNAMEINUSE:
                    return IrcReturns.ERR_NICKNAMEINUSE;
                case IrcResponseCode.ERR_CHANNELISFULL:
                    return string.Format(
                        CultureInfo.CurrentUICulture, 
                        IrcReturns.ERR_CHANNELISFULL, 
                        ExtractLeadingStringFromMessage(message));
                case IrcResponseCode.ERR_INVITEONLYCHAN:
                    return string.Format(
                        CultureInfo.CurrentUICulture, 
                        IrcReturns.ERR_INVITEONLYCHAN, 
                        ExtractLeadingStringFromMessage(message));
                default:
                    return message.Substring(3);
            }
        }

        /// <summary>
        /// Clean up the results of the asynchronous receive operation
        /// </summary>
        /// <param name="result">
        /// The results of the operation 
        /// </param>
        private void MessageReceivedCompleted(IAsyncResult result)
        {
            AsyncResult re = result as AsyncResult;
            EventHandler<PrivateMessageRecievedEventArgs> e =
                re.AsyncDelegate as EventHandler<PrivateMessageRecievedEventArgs>;

            // clean it up with with an EndInvoke
            e.EndInvoke(result);
        }

        /// <summary>
        /// The ping server.
        /// </summary>
        private void PingServer()
        {
            while (true)
            {
                this.Ping();
                Thread.Sleep(15000);
                if (!this.keepPinging)
                {
                    break;
                }
            }
        }

        /// <summary>
        /// The reciever.
        /// </summary>
        private void Reciever()
        {
            // primary receive loop
            while (this.receive)
            {
                string received = this.reader.ReadLine();

                // Check for a privmsg as most of our traffic is going to be that
                if (received.Contains(Resources.PrivateMessage))
                {
                    // get the channel name string
                    int startIndex = received.IndexOf(Resources.PrivateMessage, StringComparison.InvariantCulture)
                                     + Resources.PrivateMessage.Length + 1;
                    string channelName = received.Substring(startIndex, received.IndexOf(' ', startIndex + 1));
                    string message = received.Substring(received.IndexOf(':') + 1);
                    string sender = received.Substring(0, received.IndexOf('!'));

                    // if we have a channel with that name send the message there
                    if (this._channelHandlers.ContainsKey(channelName))
                    {
                        // invoke asynchronously so we don't block
                        this._channelHandlers[channelName].BeginInvoke(
                            this, 
                            new PrivateMessageRecievedEventArgs(sender, message), 
                            this.MessageReceivedCompleted, 
                            null);
                    }

                    continue;
                }

                int response = 0;
                int.TryParse(received.Substring(0, 3), out response);
                if (response != 0)
                {
                    this._channelHandlers[IrcConnection.SERVER_CHANNEL].BeginInvoke(
                        this, 
                        new PrivateMessageRecievedEventArgs(
                            null, IrcConnection.GetLocalizedResponseForNumericReply(response, received)), 
                        this.MessageReceivedCompleted, 
                        null);
                }
            }
        }

        #endregion
    }
}